package com.tool.emulator.apple2;

import com.tool.emulator.apple2.runtime.EmuState;
import com.tool.emulator.common.AutoDevice;
import com.tool.emulator.common.IODevice;

import java.util.LinkedList;

/**
 * Speaker.java @author tl 2011-6-21
 */
public class Speaker extends AutoDevice implements IODevice {
	public static final int SPEAKER_VOL = 0xA0;
	public static final double SILENCE_TIME = 0.2;//second;
	public static final long FLUSH_FREQ = 20; //0.05
	protected final Apple2 apple2;
	private final LinkedList<Double> queue = new LinkedList<Double>();
	private volatile SpeakerUI ui = null;
	private long lastCycles = 0;
	private boolean vol = false;
	private long changeCycles = 0;

	public Speaker(Apple2 apple2) {
		super("Speaker", FLUSH_FREQ);
		this.apple2 = apple2;
		apple2.BUS.initIO(Apple2.ADDR_SPEAKER, 1, this);
		try {
			this.ui = new SpeakerUIImpl();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	@Override
	public void deviceSaveState(EmuState emuState) throws Exception {
		//ignore
		return;
	}

	@Override
	public void deviceLoadState(EmuState emuState) throws Exception {
		//ignore
		return;
	}

	public SpeakerUI getUI() {
		return this.ui;
	}

	public void setUI(SpeakerUI ui) {
		this.ui = ui;
	}

	@Override
	protected boolean deviceStep() {
		SpeakerUI ui = this.getUI();
		for (; ; ) {
			double time;
			synchronized (this.queue) {
				if (queue.size() == 0)
					break;
				time = queue.removeFirst();
			}
			changeCycles = this.getCycles();
			if (ui != null) {
				int value = time < 0 ? 0xff : 0;
				time = Math.abs(time);
				if (time > SILENCE_TIME) {
					//when slience, enter next cycle to buffer data
					ui.reset();
					break;
				}
				ui.write(value, time);
			}
		}
		this.current_cycles++;
		if (this.getCycles() - this.changeCycles >= this.getFreq()) {
			if (ui != null)
				ui.reset();
		}
		return true;
	}

	@Override
	protected void deviceInit() throws Exception {
		super.deviceInit();
		SpeakerUI ui = this.getUI();
		if (ui != null)
			ui.start();
	}

	@Override
	protected void deviceExit() throws Exception {
		super.deviceExit();
		SpeakerUI ui = this.getUI();
		if (ui != null)
			ui.stop();
	}

	public int flipFlop(long cycles) {
		this.setAccessingTime();
		long delta = cycles - lastCycles;
		this.lastCycles = cycles;
		double time = (double) delta / (double) apple2.CPU.getFreq();
		synchronized (queue) {
			queue.add(vol ? -time : time);
		}
		vol = !vol;
		return vol ? SPEAKER_VOL : 0;

	}

	public int flipFlop() {
		return flipFlop(apple2.CPU.getCycles());
	}

	public int read_io(int addr) {
		return this.flipFlop();
	}

	public void write_io(int addr, int val) {
		//System.out.println("beep write " + val);
		read_io(addr);
		read_io(addr);
	}

}
